module('lgx.gens.luabind', package.seeall)
local decl     = require'lgx.decl'
local strbuf   = require'lgx.strbuf'
local utils    = require'lgx.utils'
--operators support by luabind
local opmap = {
    ['+']  = true , ['-']  = true ,
    ['*']  = false , ['/']  = true ,
    ['<']  = true , ['<='] = true ,
    ['=='] = true , ['()'] = true ,
}

gendata = {}
gendata.__index = gendata

function gendata.new(o)
    o = o or {}
    o.head = strbuf.new()
    o.wrap = strbuf.new()
    o.code = strbuf.new()
    setmetatable(o, gendata)
    return o
end

function gendata:addhead(data)
    self.head = self.head + data
end

function gendata:addwrap(data)
    self.wrap = self.wrap + data
end

function gendata:addcode(data)
    self.code = self.code + data
end



NS        = 'luabind'
WPNS      = 'luagccxml'
WPSUFFIX  = '_wrapper'
DEFINDENT = 2
WPINDENT  = 1
HEADER    =
[[
#include <lua.hpp>
#include <luabind/luabind.hpp>
#include <luabind/class.hpp>
#include <luabind/object.hpp>
#include <luabind/operator.hpp>
#include <luabind/function.hpp>
#include "luagccxml.hpp"
]]

function ns(str)
    return str and NS..'::'..str or NS..'::'
end

function nsname(str)
    return ns()..str
end

function wpns(str)
    return str and WPNS..'::'..str or WPNS..'::'
end


local function wpname(self)
    if self:isclass() then
        return self:name_(nil, WPSUFFIX)
    else
        return self:nsname_(WPNS, nil)
    end
end

local function wpnsname(self)
    if self:isclass() then
        return wpns(self:wpname())
    else
        return self.ids[self.context]:wpnsname()..'::'..self:wpname()
    end
end

local function addproperty(self, name, getter, setter)
    assert(self:isclass(), 'addproperty() only can be called with Class or Struct')
    local gettercell = self:getmember{name=getter}
    local settercell = self:getmember{name=setter}
    if not gettercell or not settercell then
        error('addproperty() failed because getter of setter not found', 2)
        return
    end
    gettercell.ignore = true
    settercell.ignore = true
    self.properties[name] = {getter=gettercell:typename(), setter=settercell:typename()}
end

local function fpargs(self)
    local tab   = {}
    local empty = true
    for i,v in ipairs(self.args) do
        local info = self:typeinfo(v.type)
        local fpstr = info.tdef or info.functype
        if info.functype and not tab[fpstr] then
            empty = false
            tab[fpstr] = info
        end
    end
    return empty and nil or tab
end

local function isellipsis(declitem)
    if declitem.ellipsis then return true end
    for i,v in ipairs(declitem.args) do
        local name = declitem:typename(v.type)
        if name == 'va_list' or name == 'char*' then
            return true
        end
    end
    return false
end

function newcell(cell)
    cell             = cell or {}
    cell.wrap        = (cell.virtual == '1')
    cell.overloaded  = false
    cell.alias       = nil
    cell.wpname      = wpname
    cell.wpnsname    = wpnsname
    if cell:isclass() then
        cell.addproperty = addproperty
        cell.properties  = {}
    end
    if cell.tag == decl.VAR then
        cell.ignore = true
    end
    if cell:isfunction() then
        cell.fpargs = fpargs
        --if any arg's type is va_list then make it elilipsis
        cell.ellipsis = cell.ellipsis and true or isellipsis(cell.decl)
    end
    if cell.tag == decl.CTOR and cell.decl:iscopyctor() and cell.access ~='public' then
        cell.decl:ctx().uncopy = true
    end
    if (not cell.demangled) and cell.name then
        cell.demangled = cell.name
    end
    return cell
end

------------------------------------------------------------------------
--  pre process codecell for generate class code.
--  1 check overloaded method
--  2 check abstract class, if is, no ctor needed
------------------------------------------------------------------------
local function preclass(cell)
	local mlist = cell:idlist('members')
	-- check overloaded methods and wrap
	local tab = {}
    local wrap = false
	for i,v in ipairs(mlist) do
        cell.wrap = cell.wrap or v.wrap
        if v.tag == decl.CTOR and cell.abstract then
            v.abstract = cell.abstract
        end
		local samenamecell = tab[v.name]
		if samenamecell and samenamecell.tag == v.tag then
			samenamecell.overloaded = true
			v.overloaded = true
		elseif v.tag == decl.METHOD or v.tag == decl.OP then
			tab[v.name] = v
		end
	end

end

local wpclasstemplate =
[[struct $(wpclassname) : public $(classname), public $(ns)wrap_base
{
$(memberwp)
};
$(head)
]]
local classtemplate =
[[$(class_)<$(class_arg)>("$(bindname)")$(members)]]
function Class(cell, cf)
    if cell.ignore then return nil end
	preclass(cell)
    local wpclassnsname = cell:wpnsname()
    local classname     = cell:nsname()
    local classbases    = bases(cell)
    local class_arg     = classname
                          ..(cell.wrap and (', '..wpclassnsname) or '')
                          ..(#classbases==0 and '' or ', '..classbases)
    local membercode, memberwrap, memberhead, memberlbwrap = members(cell)
    local memberheadcode = tostring(memberhead)
    local wprepmap = {
        ['$(head)']        = memberheadcode,
        ['$(wpclassname)'] = cell:wpname(),
        ['$(classname)']   = classname,
        ['$(memberwp)']    = tostring(memberwrap),
        ['$(ns)']          = ns()
    }
    local repmap = {
        ['$(class_)']    = ns('class_'),
        ['$(class_arg)'] = class_arg,
        ['$(bindname)']  = cell.alias or cell:safename(),
        ['$(members)']   = tostring(membercode),
    }

    return utils.template(classtemplate, repmap),
           #memberwrap==0 and memberheadcode or utils.template(wpclasstemplate, wprepmap), memberlbwrap
end

Struct = Class
Union  = Class
local wpctortemplate =
'$(ctorname)($(ctorargs)):$(basename)($(basectorargs)),$(wrap_base)(){}    /*ID:$(id)*/\n'
function Constructor(cell, cf)
    if cell.ignore or cell.abstract or cell.access~='public' then return '' end
    local ctorfmt = '\n    .def('..ns('constructor<%s>())')
    local buf = strbuf.new()
    buf = buf + string.format(ctorfmt, cell:protoargs())
    local wrap = nil
    local ctx = cell:ctx()
    if ctx.wrap then
        local repmap = {
            ['$(id)']           = cell.id,
            ['$(ctorname)']     = ctx:wpname(),
            ['$(ctorargs)']     = cell:declargs(),
            ['$(basename)']     = ctx:nsname(),
            ['$(basectorargs)'] = cell:callargs(),
            ['$(wrap_base)']    = ns('wrap_base'),
        }
        wrap = utils.template(wpctortemplate, repmap)
        if cell.defpos > 0 then
            local wraps = strbuf.new()
            local dargs = cell:declargs_()
            local cargs = cell:callargs_()
            local pargs = cell:protoargs_()
            for i=cell.defpos-1, #cell.args-1 do
                repmap['$(ctorargs)'] = i>0 and dargs:concat(', ', 1, i) or ''
                repmap['$(basectorargs)'] = i>0 and cargs:concat(', ', 1, i) or ''
                wraps = wraps + utils.template(wpctortemplate, repmap)
                buf = buf + string.format(ctorfmt, pargs:concat(', ', 1, i) or '')
            end
            wrap = wrap .. wraps:concat()
        end
    end
	return buf, wrap
end

function Destructor(cell, cf)
    if cell.ignore then return nil end
	return nil
end

local fldtemplate = '\n    $(def)("$(name)", &$(typename))'
function Field(cell, cf)
    if cell.ignore or cell.access~='public' or cell:typeinfo(cell.type).cvtype then return nil end
    return utils.template(fldtemplate, {
        ['$(def)']      = cell.readonly and '.def_readonly' or '.def_readwrite',
        ['$(name)']     = cell.alias or cell.name,
        ['$(typename)'] = cell:typename(),
    })
end

------------------------------------------------------------------------
-- OperatorMethod code gen
------------------------------------------------------------------------
local optemplate =
'\n    .def($(lhs)$(op)$(rhs))    /*ID:$(id)*/'
function OperatorMethod(cell, cf)
    if cell.ignore or cell.access~='public' then return nil end

    --luabind only support + - * / == < <= ()
    if not opmap[cell.name] then return nil end
    local lhs = ''
    local rhs = ''
    local op  = ''
    if cell.name == '()' then
        lhs = cell.const and strbuf.new(ns('const_self('))
                         or  strbuf.new(ns('self('))
        for i,v in ipairs(cell.args) do
            lhs = lhs + ns('other<') + cell:typename(v.type) + '>()'
                  + (i==#cell.args and '' or ',')
        end
        lhs = tostring(lhs + ')')
    else
        lhs = cell.const and ns('const_self') or ns('self')
        if #cell.args < 1 then
            lhs = cell.name..lhs
        else
            assert(#cell.args==1,string.format('operator %s must take one arg', cell.name))
            rhs = ns('other<')..cell:typename(cell.args[1].type)..'>()'
            op  = ' '..cell.name..' '
        end
    end
    return utils.template(optemplate, {['$(lhs)']=lhs, ['$(op)']=op, ['$(rhs)']=rhs, ['$(id)']=cell.id}), nil
end

------------------------------------------------------------------------
-- Method code gen
------------------------------------------------------------------------
local wpmethodtemplate =
[[
virtual $(rettype) $(name)($(declargs)) $(const)    /*ID:$(id)*/
{
    $(return)$(ns)::call_member<$(rettype)>(this, "$(name)"$(callargs));
}
static $(srettype) $(wpname)($(classname)* _thisptr$(sdeclargs))
{
    $(scontent)
}
]]
function Method(cell, cf)
    if cell.ignore or cell.access~='public' or cell.ellipsis then return '' end
    -- if any arg is by-value and whose type is uncopy able, ignore
    for i,v in ipairs(cell.args) do
        local info = cell:typeinfo(v.type)
        if info.decl.uncopy and not info.ptype then
            return ''
        end
    end
    --generate bind code
	local prototype   = ''
	local wpprototype = ''
	local classname   = cell.ids[cell.context]:nsname()
	local wrapbuf     = strbuf.new()
	local headbuf     = strbuf.new()
	local tdefname    = ''
	local wptdefname  = ''
	if cell.overloaded then
        tdefname = 'T'..cell:nsnamex_()..'_pfunc'..(cell.const and '_const' or '')
		prototype = string.format('typedef %s (%s*%s)(%s)%s;\n',
			cell:typename(cell.returns),
            cell.static and '' or classname..'::',
            tdefname,
            args(cell),
            cell.const and ' const' or ''
        )
        if cell.wrap then
            wptdefname = tdefname..'_wrap'
            wpprototype = string.format('typedef %s (*%s)(%s);\n',
                cell:typename(cell.returns),
                wptdefname,
                classname..'*'..(#cell.args>0 and (', '..args(cell)) or '')
            )
        end
        headbuf = headbuf + prototype + wpprototype
	end
    local tdefcast = cell.overloaded and '('..tdefname..') ' or '' 
    local wptdefcast = #wpprototype>1 and '('..wptdefname..') ' or ''
	local code = string.format('\n    .def("%s",%s&%s%s)    /*ID:%s*/',
		cell.alias or cell.name, 
        tdefcast, 
        cell:nsname(),
        cell.wrap and ', '..wptdefcast..'&'..cell:wpnsname() or '',
        cell.id
    )
    -- generate warpper code
    if cell.wrap then
        local const = cell.const == '1' and 'const' or ''
        local callargs = cell:callargs()
        local declargs = cell:declargs()
        local rettype = cell:typename(cell.returns)
		local ret = rettype == 'void' and '' or 'return '
		local scontent = cell.pure_virtual and 'throw std::runtime_error("call abstract function '..cell:nsname()..'");' or
						 string.format('%s_thisptr->%s::%s(%s);', ret, classname, cell.alias or cell.name, callargs)
        local repmap = {
            ['$(id)']         = cell.id,
            ['$(rettype)']    = rettype,
            ['$(return)']     = rettype == 'void' and '' or 'return ',
            ['$(wpname)']     = cell:wpname(),
            ['$(classname)']  = classname,
            ['$(name)']       = cell.alias or cell.name,
            ['$(callargs)']   = #callargs==0 and '' or ', '..callargs,
            ['$(wpcallargs)'] = callargs,
            ['$(sdeclargs)']  = #declargs==0 and '' or ', '..declargs,
            ['$(declargs)']   = #declargs==0 and '' or declargs,
            ['$(const)']      = const,
            ['$(ns)']         = NS,
			['$(srettype)']   = cell.pure_virtual and 'void' or rettype,
			['$(scontent)']   = scontent,
        }
        wrapbuf = wrapbuf + utils.template(wpmethodtemplate, repmap)
    end
    return code, tostring(wrapbuf), tostring(headbuf)
end

function Function(cell, cf)
    local functemplate = '\n'..ns('def("%s", &%s)')
    return string.format(functemplate, cell.name, cell:nsname()), nil
end

local function ignoredecl(cell)
    return nil
end

Typedef          = ignoredecl
Converter        = ignoredecl
ArrayType        = ignoredecl
FunctionType     = ignoredecl
OffsetType       = ignoredecl
Variable         = ignoredecl
FundamentalType  = ignoredecl
--OperatorFunction = ignoredecl
--not implement yet...
Enumeration      = ignoredecl
------------------------------------------------------------------------
--  generate bases<base1, base2...>
------------------------------------------------------------------------
function bases(cell)
    local baselist = cell.baselist
    if #baselist > 0 then
        local buf = strbuf.new()
        for i=1,#baselist do
            local base = cell.ids[baselist[i].type]
            if cell.owner:contained(base) then
                buf = buf + base:nsname()
            end
        end
        --buf = buf + ns('bases<') + cell.ids[baselist[1].type]:nsname()
        --for i=2,#baselist do
        --    buf = buf + ' ,' + cell.ids[baselist[i].type]:nsname()
        --end
        --buf = buf + ' > '
        if #buf > 0 then
            return ns('bases<')..buf:concat(',')..' > '
        end
    end
    return ''
end

local function istostring(cell, opdecl, linfo, rinfo)
    if opdecl.name ~= '<<' then return false end
    if #opdecl.args ~= 2 then return false end

    if not linfo.decl then return false end
    if linfo.decl.demangled ~= 'std::ostream' then return false end

    if not rinfo.decl then return false end
    if rinfo.decl.id ~= cell.id then return false end

    return true
end

local function genopfunc(cell, opdecl)
    --.def(other<otype>() + self)
    --.def(self + other<otype>())
    local linfo = opdecl.args[1] and cell:typeinfo(opdecl.args[1].type) or nil
    local rinfo = opdecl.args[2] and cell:typeinfo(opdecl.args[2].type) or nil
    if not (linfo and rinfo) then
        return ''
    end
    --assert(linfo and rinfo, string.format('OperatorFunction %s[%s]should have 2 args but we get only 1', opdecl:nsname(), opdecl.id))
    if istostring(cell, opdecl, linfo, rinfo) then
        return '\n    .def('..ns('tostring(')..(rinfo.const and ns('const_self') or ns('self'))..'))'
    end

    local opftemplate = '\n    .def($(lhs) $(op) $(rhs))'
    local lhs = nil
    local rhs = nil
    if linfo.decl and (linfo.decl.id == cell.id) then
        lhs = linfo.const and ns('const_self') or ns('self')
    else
        lhs = ns('other<')..linfo.name..'>()'
    end
    if rinfo.decl and (rinfo.decl.id == cell.id) then
        rhs = rinfo.const and ns('const_self') or ns('self')
    else
        rhs = ns('other<')..rinfo.name..'>()'
    end
    return utils.template(opftemplate, {['$(lhs)']=lhs, ['$(op)']=opdecl.name, ['$(rhs)']=rhs})
end

local lbtemplate = 
[[template<> struct default_converter<$(fpstr)>:public luagccxml::fp_converter<$(fpstr)>{};]]
function members(cell)
    local memlist = cell:idlist('members')
    local codes   = strbuf.new()
    local wraps   = strbuf.new()
    local heads   = strbuf.new()
    local lbwrap  = strbuf.new()
    local fpfound = {}
    for i,v in ipairs(memlist) do
        if not v.ignore and (not v:isclass()) and (v.access and v.access == 'public' or true) then
            local code, wrap, head = v:gencode()
            codes = codes + code
            wraps = wraps + wrap
            heads = heads + head
        end
        --if is function and have funtion pointer args, gen default_converter
        if v.fpargs then
            local fpargs = v:fpargs()
            if fpargs then
                for k,v in pairs(fpargs) do
                    if not fpfound[k] then
                        fpfound[k] = true
                        lbwrap = lbwrap + utils.template(lbtemplate, {['$(fpstr)'] = k})
                    end
                end
            end
        end
    end
    if cell.opfuncs then
        for i,v in ipairs(cell.opfuncs) do
            if opmap[v.name] then
                codes = codes + genopfunc(cell, v)
            end
        end
    end
    if #cell.properties > 0 then
        local proptemplate = '\n    .property("$(name)", &$(getter), &$(setter))'
        for k,v in pairs(cell.properties) do
            codes = codes + template(proptemplate, {
                ['$(name)']   = k,
                ['$(setter)'] = v.setter,
                ['$(getter)'] = v.getter,
            })
        end
    end
    
    return codes, wraps, heads, lbwrap:concat('\n')
end

function args(cell)
	if not cell.args then return '' end
	local str = ''
	for i,v in ipairs(cell.args) do
		str = str .. cell:typename(v.type) .. ', '
	end
	return string.sub(str, 1, #str-2)
end
